import React, { useEffect, useState } from 'react';
import { useDebounce } from 'react-use';

import { ConnectionConfig } from '@grafana/aws-sdk';
import {
  rangeUtil,
  DataSourcePluginOptionsEditorProps,
  onUpdateDatasourceJsonDataOption,
  updateDatasourcePluginJsonDataOption,
  DataSourceTestSucceeded,
  DataSourceTestFailed,
} from '@grafana/data';
import { ConfigSection } from '@grafana/experimental';
import { getAppEvents, usePluginInteractionReporter } from '@grafana/runtime';
import { Input, InlineField, FieldProps, SecureSocksProxySettings, Field, Divider } from '@grafana/ui';
import { notifyApp } from 'app/core/actions';
import { config } from 'app/core/config';
import { createWarningNotification } from 'app/core/copy/appNotification';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { store } from 'app/store/store';

import { CloudWatchDatasource } from '../../datasource';
import { SelectableResourceValue } from '../../resources/types';
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../../types';
import { LogGroupsFieldWrapper } from '../shared/LogGroups/LogGroupsField';

import { SecureSocksProxySettingsNewStyling } from './SecureSocksProxySettingsNewStyling';
import { XrayLinkConfig } from './XrayLinkConfig';

export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData, CloudWatchSecureJsonData>;

type LogGroupFieldState = Pick<FieldProps, 'invalid'> & { error?: string | null };

export const ConfigEditor = (props: Props) => {
  const { options, onOptionsChange } = props;
  const { defaultLogGroups, logsTimeout, defaultRegion, logGroups } = options.jsonData;
  const datasource = useDatasource(props);
  useAuthenticationWarning(options.jsonData);
  const logsTimeoutError = useTimoutValidation(logsTimeout);
  const saved = useDataSourceSavedState(props);
  const [logGroupFieldState, setLogGroupFieldState] = useState<LogGroupFieldState>({
    invalid: false,
  });
  const newFormStylingEnabled = config.featureToggles.awsDatasourcesNewFormStyling;
  useEffect(() => setLogGroupFieldState({ invalid: false }), [props.options]);
  const report = usePluginInteractionReporter();
  useEffect(() => {
    const successSubscription = getAppEvents().subscribe<DataSourceTestSucceeded>(DataSourceTestSucceeded, () => {
      report('grafana_plugin_cloudwatch_save_succeeded', {
        auth_type: options.jsonData.authType,
      });
    });
    const failSubscription = getAppEvents().subscribe<DataSourceTestFailed>(DataSourceTestFailed, () => {
      report('grafana_plugin_cloudwatch_save_failed', {
        auth_type: options.jsonData.authType,
      });
    });
    return () => {
      successSubscription.unsubscribe();
      failSubscription.unsubscribe();
    };
  }, [options.jsonData.authType, report]);
  const [externalId, setExternalId] = useState('');
  useEffect(() => {
    if (!externalId && datasource) {
      datasource.resources
        .getExternalId()
        .then(setExternalId)
        .catch(() => setExternalId('Unable to fetch externalId'));
    }
  }, [datasource, externalId]);

  return newFormStylingEnabled ? (
    <div className="width-30">
      <ConnectionConfig
        {...props}
        newFormStylingEnabled={true}
        loadRegions={
          datasource &&
          (async () => {
            return datasource.resources
              .getRegions()
              .then((regions) =>
                regions.reduce(
                  (acc: string[], curr: SelectableResourceValue) => (curr.value ? [...acc, curr.value] : acc),
                  []
                )
              );
          })
        }
        externalId={externalId}
      />
      {config.secureSocksDSProxyEnabled && (
        <SecureSocksProxySettingsNewStyling options={options} onOptionsChange={onOptionsChange} />
      )}
      <Divider />
      <ConfigSection title="Cloudwatch Logs">
        <Field
          htmlFor="logsTimeout"
          label="Query Result Timeout"
          description='Grafana will poll for Cloudwatch Logs results every second until Done status is returned from AWS or timeout is exceeded, in which case Grafana will return an error. Note: For Alerting, the timeout from Grafana config file will take precedence. Must be a valid duration string, such as "30m" (default) "30s" "2000ms" etc.'
          invalid={Boolean(logsTimeoutError)}
        >
          <Input
            id="logsTimeout"
            width={60}
            placeholder="30m"
            value={options.jsonData.logsTimeout || ''}
            onChange={onUpdateDatasourceJsonDataOption(props, 'logsTimeout')}
            title={'The timeout must be a valid duration string, such as "15m" "30s" "2000ms" etc.'}
          />
        </Field>
        <Field
          label="Default Log Groups"
          description="Optionally, specify default log groups for CloudWatch Logs queries."
          {...logGroupFieldState}
        >
          {datasource ? (
            <LogGroupsFieldWrapper
              newFormStylingEnabled={true}
              region={defaultRegion ?? ''}
              datasource={datasource}
              onBeforeOpen={() => {
                if (saved) {
                  return;
                }

                let error = 'You need to save the data source before adding log groups.';
                if (props.options.version && props.options.version > 1) {
                  error =
                    'You have unsaved connection detail changes. You need to save the data source before adding log groups.';
                }
                setLogGroupFieldState({
                  invalid: true,
                  error,
                });
                throw new Error(error);
              }}
              legacyLogGroupNames={defaultLogGroups}
              logGroups={logGroups}
              onChange={(updatedLogGroups) => {
                onOptionsChange({
                  ...props.options,
                  jsonData: {
                    ...props.options.jsonData,
                    logGroups: updatedLogGroups,
                    defaultLogGroups: undefined,
                  },
                });
              }}
              maxNoOfVisibleLogGroups={2}
              //legacy props
              legacyOnChange={(logGroups) => {
                updateDatasourcePluginJsonDataOption(props, 'defaultLogGroups', logGroups);
              }}
            />
          ) : (
            <></>
          )}
        </Field>
      </ConfigSection>
      <Divider />
      <XrayLinkConfig
        newFormStyling={true}
        onChange={(uid) => updateDatasourcePluginJsonDataOption(props, 'tracingDatasourceUid', uid)}
        datasourceUid={options.jsonData.tracingDatasourceUid}
      />
    </div>
  ) : (
    <>
      <ConnectionConfig
        {...props}
        labelWidth={29}
        loadRegions={
          datasource &&
          (async () => {
            return datasource.resources
              .getRegions()
              .then((regions) =>
                regions.reduce(
                  (acc: string[], curr: SelectableResourceValue) => (curr.value ? [...acc, curr.value] : acc),
                  []
                )
              );
          })
        }
        externalId={externalId}
      >
        <InlineField label="Namespaces of Custom Metrics" labelWidth={29} tooltip="Namespaces of Custom Metrics.">
          <Input
            width={60}
            placeholder="Namespace1,Namespace2"
            value={options.jsonData.customMetricsNamespaces || ''}
            onChange={onUpdateDatasourceJsonDataOption(props, 'customMetricsNamespaces')}
          />
        </InlineField>
      </ConnectionConfig>

      {config.secureSocksDSProxyEnabled && (
        <SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
      )}

      <h3 className="page-heading">CloudWatch Logs</h3>
      <div className="gf-form-group">
        <InlineField
          label="Query Result Timeout"
          labelWidth={28}
          tooltip='Grafana will poll for Cloudwatch Logs query results every second until Done status is returned from AWS or timeout is exceeded, in which case Grafana will return an error. The default period is 30 minutes. Note: For Alerting, the timeout defined in the config file will take precedence. Must be a valid duration string, such as "15m" "30s" "2000ms" etc.'
          invalid={Boolean(logsTimeoutError)}
        >
          <Input
            width={60}
            placeholder="30m"
            value={options.jsonData.logsTimeout || ''}
            onChange={onUpdateDatasourceJsonDataOption(props, 'logsTimeout')}
            title={'The timeout must be a valid duration string, such as "15m" "30s" "2000ms" etc.'}
          />
        </InlineField>
        <InlineField
          label="Default Log Groups"
          labelWidth={28}
          tooltip="Optionally, specify default log groups for CloudWatch Logs queries."
          shrink={true}
          {...logGroupFieldState}
        >
          {datasource ? (
            <LogGroupsFieldWrapper
              region={defaultRegion ?? ''}
              datasource={datasource}
              onBeforeOpen={() => {
                if (saved) {
                  return;
                }

                let error = 'You need to save the data source before adding log groups.';
                if (props.options.version && props.options.version > 1) {
                  error =
                    'You have unsaved connection detail changes. You need to save the data source before adding log groups.';
                }
                setLogGroupFieldState({
                  invalid: true,
                  error,
                });
                throw new Error(error);
              }}
              legacyLogGroupNames={defaultLogGroups}
              logGroups={logGroups}
              onChange={(updatedLogGroups) => {
                onOptionsChange({
                  ...props.options,
                  jsonData: {
                    ...props.options.jsonData,
                    logGroups: updatedLogGroups,
                    defaultLogGroups: undefined,
                  },
                });
              }}
              maxNoOfVisibleLogGroups={2}
              //legacy props
              legacyOnChange={(logGroups) => {
                updateDatasourcePluginJsonDataOption(props, 'defaultLogGroups', logGroups);
              }}
            />
          ) : (
            <></>
          )}
        </InlineField>
      </div>
      <XrayLinkConfig
        onChange={(uid) => updateDatasourcePluginJsonDataOption(props, 'tracingDatasourceUid', uid)}
        datasourceUid={options.jsonData.tracingDatasourceUid}
      />
    </>
  );
};

function useAuthenticationWarning(jsonData: CloudWatchJsonData) {
  const addWarning = (message: string) => {
    store.dispatch(notifyApp(createWarningNotification('CloudWatch Authentication', message)));
  };

  useEffect(() => {
    if (jsonData.authType === 'arn') {
      addWarning('Since grafana 7.3 authentication type "arn" is deprecated, falling back to default SDK provider');
    } else if (jsonData.authType === 'credentials' && !jsonData.profile && !jsonData.database) {
      addWarning(
        'As of grafana 7.3 authentication type "credentials" should be used only for shared file credentials. \
             If you don\'t have a credentials file, switch to the default SDK provider for extracting credentials \
             from environment variables or IAM roles'
      );
    }
  }, [jsonData.authType, jsonData.database, jsonData.profile]);
}

function useDatasource(props: Props) {
  const [datasource, setDatasource] = useState<CloudWatchDatasource>();

  useEffect(() => {
    if (props.options.version) {
      getDatasourceSrv()
        .loadDatasource(props.options.name)
        .then((datasource) => {
          if (datasource instanceof CloudWatchDatasource) {
            setDatasource(datasource);
          }
        });
    }
  }, [props.options.version, props.options.name]);

  return datasource;
}

function useTimoutValidation(value: string | undefined) {
  const [err, setErr] = useState<undefined | string>(undefined);
  useDebounce(
    () => {
      if (value) {
        try {
          rangeUtil.describeInterval(value);
          setErr(undefined);
        } catch (e) {
          if (e instanceof Error) {
            setErr(e.toString());
          }
        }
      } else {
        setErr(undefined);
      }
    },
    350,
    [value]
  );
  return err;
}

function useDataSourceSavedState(props: Props) {
  const [saved, setSaved] = useState(!!props.options.version && props.options.version > 1);
  useEffect(() => {
    setSaved(false);
  }, [
    props.options.jsonData.assumeRoleArn,
    props.options.jsonData.authType,
    props.options.jsonData.defaultRegion,
    props.options.jsonData.endpoint,
    props.options.jsonData.externalId,
    props.options.jsonData.profile,
    props.options.secureJsonData?.accessKey,
    props.options.secureJsonData?.secretKey,
  ]);

  useEffect(() => {
    props.options.version && props.options.version > 1 && setSaved(true);
  }, [props.options.version]);

  return saved;
}
